/*
 * Copyright 2005 Novell, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of
 * Novell, Inc. not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * Novell, Inc. makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL NOVELL, INC. BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <Ecore.h>
#include <Ecore_X.h>
#include <Evas.h>

#include "e.h"
#include "e_border.h"

#include "compiz.h"
#include "bang.h"
#include "bang_window.h"

//# define D(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define D(x)  ((void) 0)

//# define DN(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define DN(x)  ((void) 0)

#define WIN_X(w) ((w)->attrib.x - (w)->input.left)
#define WIN_Y(w) ((w)->attrib.y - (w)->input.top)
#define WIN_W(w) ((w)->width + (w)->input.left + (w)->input.right)
#define WIN_H(w) ((w)->height + (w)->input.top + (w)->input.bottom)

#define SCALE_SPACING_DEFAULT       25
#define SCALE_SPEED_DEFAULT         2.5f // 1.5
#define SCALE_OPACITY_DEFAULT       75
#define SCALE_SLOPPY_FOCUS_DEFAULT  TRUE

#define SCALE_TIMESTEP_DEFAULT      1.2f

#define SCALE_STATE_NONE 0
#define SCALE_STATE_OUT  1
#define SCALE_STATE_WAIT 2
#define SCALE_STATE_IN   3

#define SCALE_HEAD_DEFAULT 1

static int _e_scale_cb_redraw_timer(void *data);

static int              displayPrivateIndex;
Ecore_Timer            *timer;
static Ecore_X_Window   input_window = 0;

typedef enum {
    ScaleIconNone = 0,
    ScaleIconEmblem,
    ScaleIconBig
} IconOverlay;

typedef struct _ScaleSlot {
   int x1, y1, x2, y2;
   int line;
} ScaleSlot;

typedef struct _ScaleDisplay {
   int             screenPrivateIndex;
//   HandleEventProc handleEvent;
   unsigned int    lastActiveNum;
} ScaleDisplay;

typedef struct _ScaleScreen {
   int windowPrivateIndex;

   PreparePaintScreenProc preparePaintScreen;
   DonePaintScreenProc    donePaintScreen;
   PaintScreenProc        paintScreen;
   PaintWindowProc        paintWindow;
   DamageWindowRectProc   damageWindowRect;

   int spacing;

   float speed;
   float timestep;

   int grabIndex;

   int state;
   int moreAdjust;

   ScaleSlot *slots;
   int        slotsSize;
   int        nSlots;

   int       *line;
   int        lineSize;
   int        nLine;

   /* only used for sorting */
   CompWindow **windows;
   int        windowsSize;
   int        nWindows;

   GLfloat  scale;
   GLushort opacity;
   
   Bool     allScreensMode;
   Bool     onlyCurrent;
   Window   selectedWindow;

   int head;

   IconOverlay iconOverlay;
    
} ScaleScreen;

typedef struct _ScaleWindow {
    ScaleSlot *slot;

    GLfloat xVelocity, yVelocity, scaleVelocity;
    GLfloat scale;
    GLfloat tx, ty;
    Bool    adjust;
} ScaleWindow;


#define GET_SCALE_DISPLAY(d) \
    ((ScaleDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define SCALE_DISPLAY(d) \
    ScaleDisplay *sd = GET_SCALE_DISPLAY (d)

#define GET_SCALE_SCREEN(s, sd) \
    ((ScaleScreen *) (s)->privates[(sd)->screenPrivateIndex].ptr)

#define SCALE_SCREEN(s) \
    ScaleScreen *ss = GET_SCALE_SCREEN (s, GET_SCALE_DISPLAY (s->display))

#define GET_SCALE_WINDOW(w, ss) \
    ((ScaleWindow *) (w)->privates[(ss)->windowPrivateIndex].ptr)

#define SCALE_WINDOW(w) \
    ScaleWindow *sw = GET_SCALE_WINDOW  (w, \
            GET_SCALE_SCREEN  (w->screen, \
            GET_SCALE_DISPLAY (w->screen->display)))


static Bool
scaleSetHead ( ScaleScreen *ss,
               CompScreen *s,
               int head )
{
D(("BEGIN\n"));
    if ( !s->useXinerama )
    {
        if ( head > 0 )
            return FALSE;
        else ss->head = 1;
    }
    else
    {
        if ( head == -1 )
            ss->head = -1;
        else if ( head == 0 )
            ss->head = s->headsCount + 1;
        else if ( head > s->headsCount )
            return FALSE;
        else ss->head = head;
    }

    return TRUE;
}

static Bool
isScaleWin (CompWindow *w)
{
   // SCALE_SCREEN (w->screen);

   if(e_border_find_by_window(w->id) != NULL)
   {
      D(("isScaleWin? YES\n"));
      return TRUE;
   }
   else
   {
      D(("isScaleWin? NO\n"));
      return FALSE;
   }
}

static Bool
scalePaintWindow (CompWindow		  *w,
		  const WindowPaintAttrib *attrib,
		  Region		  region,
		  unsigned int		  mask)
{
DN(("scalePaintWindow \n"));
    CompScreen *s = w->screen;
    Bool       status;

    SCALE_SCREEN (s);

    if (ss->grabIndex)
    {
	WindowPaintAttrib sAttrib = *attrib;

	SCALE_WINDOW (w);
    
	if (sw->adjust || sw->slot)
	{
	    mask |= PAINT_WINDOW_TRANSFORMED_MASK;

	    if (ss->opacity != OPAQUE			&&
           ss->state   != SCALE_STATE_IN)
	    {
		/* modify opacity of windows that are not active */
  		sAttrib.opacity = (sAttrib.opacity * ss->opacity) >> 16;
	    }
	}
	//else if (ss->darkenBack && ss->state != SCALE_STATE_IN)
	//{
	    /* modify brightness of the other windows */
 	 //   sAttrib.brightness = sAttrib.brightness / 2;
	//}

	UNWRAP (ss, s, paintWindow);
	status = (*s->paintWindow) (w, &sAttrib, region, mask);
	WRAP (ss, s, paintWindow, scalePaintWindow);

	if ((ss->iconOverlay != ScaleIconNone) && (sw->adjust || sw->slot))
	{
	    CompIcon *icon;
 
	    icon = NULL;
	    if (!icon)
		icon = w->screen->defaultIcon;
 
	    if (icon && (icon->texture.name || iconToTexture (w->screen, icon)))
	    {
		REGION	    iconReg;
		CompMatrix  matrix;
		int	    wx, wy, width, height;
		int	    scaledWinWidth, scaledWinHeight;
		float	    ds;
 
		scaledWinWidth  = w->width  * sw->scale;
		scaledWinHeight = w->height * sw->scale;
 
		switch (ss->iconOverlay) {
		case ScaleIconNone:
		case ScaleIconEmblem:
		    sAttrib.xScale = sAttrib.yScale = 1.0f;
		    break;
		case ScaleIconBig:
		    sAttrib.opacity /= 3;
		    sAttrib.xScale = sAttrib.yScale =
			MIN (((float) scaledWinWidth / (float) icon->width),
			     ((float) scaledWinHeight / (float) icon->height));
		default:
		    break;
		}
 
		width  = icon->width  * sAttrib.xScale;
		height = icon->height * sAttrib.yScale;
  
		switch (ss->iconOverlay) {
		case ScaleIconNone:
		case ScaleIconEmblem:
		    wx = w->attrib.x + (w->width  * sw->scale) - icon->width;
		    wy = w->attrib.y + (w->height * sw->scale) - icon->height;
		    break;
		case ScaleIconBig:
		    wx = w->attrib.x + ((scaledWinWidth - width) / 2) /
			sAttrib.xScale;
		    wy = w->attrib.y + ((scaledWinHeight - height) / 2) /
			sAttrib.yScale;
		    break;
		default:
		    wx = wy = 0;
		    break;
		}
  
		ds = 1.0f - ss->scale;
		if (ds)
		{
		    sAttrib.opacity =
			(fabs (1.0f - sw->scale) * sAttrib.opacity) / ds;
		}
		else if (!sw->slot)
		{
		    sAttrib.opacity = 0;
		}
 
		mask |= PAINT_WINDOW_TRANSLUCENT_MASK;
 
		iconReg.rects    = &iconReg.extents;
		iconReg.numRects = 1;
 
		iconReg.extents.x1 = wx;
		iconReg.extents.y1 = wy;
		iconReg.extents.x2 = iconReg.extents.x1 + width;
		iconReg.extents.y2 = iconReg.extents.y1 + height;
 
		matrix = icon->texture.matrix;
		matrix.x0 -= wx * icon->texture.matrix.xx;
		matrix.y0 -= wy * icon->texture.matrix.yy;
 
		w->vCount = 0;
		if (iconReg.extents.x1 < iconReg.extents.x2 &&
		    iconReg.extents.y1 < iconReg.extents.y2)
		    (*w->screen->addWindowGeometry) (w, &matrix, 1, &iconReg,
						     &iconReg);
						     
 
		if (w->vCount)
		    (*w->screen->drawWindowTexture) (w,
						     &icon->texture, &sAttrib,
						     mask);
						    
	    }
	}
   }
    else
    {
	UNWRAP (ss, s, paintWindow);
	status = (*s->paintWindow) (w, attrib, region, mask);
	WRAP (ss, s, paintWindow, scalePaintWindow);
   }
  
    return status;

}

static int
compareWindows (const void *elem1,
		const void *elem2)
{
D(("compareWindows\n"));
    CompWindow *w1 = *((CompWindow **) elem1);
    CompWindow *w2 = *((CompWindow **) elem2);

    if (!(WIN_X(w1) - WIN_X(w2)))
      return WIN_Y(w1) - WIN_Y(w2);
    return WIN_X(w1) - WIN_X(w2);
}


/* TODO: Place window thumbnails at smarter positions */
static Bool
layoutThumbs (CompScreen *s)
{
DN(("BEGIN layoutThumbs \n"));
    CompWindow *w;
    int	       i, j, y2;
    int        cx, cy;
    int        lineLength, itemsPerLine;
    float      scaleW, scaleH;
    int        totalWidth, totalHeight;
    int        areaX, areaY, areaWidth, areaHeight, headWidth, headHeight, head;

    SCALE_SCREEN (s);

    if ( ss->head == -1 ) head = screenGetCurrentHead ( s );
    else head = ss->head;

    headWidth = screenGetHeadWidth ( s, head );
    headHeight = screenGetHeadHeight ( s, head );
    areaX = screenGetHeadAreaX ( s, head );
    areaY = screenGetHeadAreaY ( s, head );
    areaWidth = screenGetHeadAreaWidth ( s, head );
    areaHeight = screenGetHeadAreaHeight ( s, head );

    cx = cy = ss->nWindows = 0;

     Eina_List *l;
     for(l = e_border_client_list(); l; l = l->next)

	    { 
	    	E_Border *bd;
	        bd = l->data;
	        if(!bd)
	         continue;
	        if(bd->desk != e_desk_current_get(bd->desk->zone)) //TODO all desk mode
	           e_border_show(bd);
	        w = findWindowAtDisplay (s->display, bd->win);
	        if(!w)
	         continue;
//	        w = bd->comp_win;
            
            SCALE_WINDOW (w);

	
		if (sw->slot)
		sw->adjust = TRUE;
	
		sw->slot = 0;

		if (ss->windowsSize <= ss->nWindows)
		{
		ss->windows = realloc (ss->windows,
					sizeof (CompWindow *) * (ss->nWindows + 32));
		if (!ss->windows)
			return FALSE;
	
		ss->windowsSize = ss->nWindows + 32;
		}
	
		ss->windows[ss->nWindows++] = w;
	}

    if (ss->nWindows == 0)
	return FALSE;

    qsort (ss->windows, ss->nWindows, sizeof (CompWindow *), compareWindows);

    itemsPerLine = (sqrt (ss->nWindows) * headWidth) / headHeight;
    if (itemsPerLine < 1)
	itemsPerLine = 1;

    if (ss->lineSize <= ss->nWindows / itemsPerLine + 1)
    {
	ss->line = realloc (ss->line, sizeof (int) *
			    (ss->nWindows / itemsPerLine + 2));
	if (!ss->line)
	    return FALSE;

	ss->lineSize = ss->nWindows / itemsPerLine + 2;
    }

    totalWidth = totalHeight = 0;

    ss->line[0] = 0;
    ss->nLine = 1;
    lineLength = itemsPerLine;

    if (ss->slotsSize <= ss->nWindows)
    {
	ss->slots = realloc (ss->slots, sizeof (ScaleSlot) *
			     (ss->nWindows + 1));
	if (!ss->slots)
	    return FALSE;

	ss->slotsSize = ss->nWindows + 1;
    }
    ss->nSlots = 0;

    for (i = 0; i < ss->nWindows; i++)
    {
	SCALE_WINDOW (ss->windows[i]);

	w = ss->windows[i];

	/* find a good place between other elements */
	for (j = 0; j < ss->nSlots; j++)
	{
	    y2 = ss->slots[j].y2 + ss->spacing + WIN_H (w);
	    if (w->width < ss->slots[j].x2 - ss->slots[j].x1 &&
		y2 <= ss->line[ss->slots[j].line])
		break;
	}

	/* otherwise append or start a new line */
	if (j == ss->nSlots)
	{
	    if (lineLength < itemsPerLine)
	    {
		lineLength++;

		ss->slots[ss->nSlots].x1 = cx;
		ss->slots[ss->nSlots].y1 = cy;
		ss->slots[ss->nSlots].x2 = cx + WIN_W (w);
		ss->slots[ss->nSlots].y2 = cy + WIN_H (w);
		ss->slots[ss->nSlots].line = ss->nLine - 1;

		ss->line[ss->nLine - 1] = MAX (ss->line[ss->nLine - 1],
					       ss->slots[ss->nSlots].y2);
	    }
	    else
	    {
		lineLength = 1;

		cx = ss->spacing;
		cy = ss->line[ss->nLine - 1] + ss->spacing;

		ss->slots[ss->nSlots].x1 = cx;
		ss->slots[ss->nSlots].y1 = cy;
		ss->slots[ss->nSlots].x2 = cx + WIN_W (w);
		ss->slots[ss->nSlots].y2 = cy + WIN_H (w);
		ss->slots[ss->nSlots].line = ss->nLine - 1;

		ss->line[ss->nLine] = ss->slots[ss->nSlots].y2;

		ss->nLine++;
	    }

	    if (ss->slots[ss->nSlots].y2 > totalHeight)
		totalHeight = ss->slots[ss->nSlots].y2;
	}
	else
	{
	    ss->slots[ss->nSlots].x1 = ss->slots[j].x1;
	    ss->slots[ss->nSlots].y1 = ss->slots[j].y2 + ss->spacing;
	    ss->slots[ss->nSlots].x2 = ss->slots[ss->nSlots].x1 + WIN_W (w);
	    ss->slots[ss->nSlots].y2 = ss->slots[ss->nSlots].y1 + WIN_H (w);
	    ss->slots[ss->nSlots].line = ss->slots[j].line;

	    ss->slots[j].line = 0;
	}

	cx = ss->slots[ss->nSlots].x2;
	if (cx > totalWidth)
	    totalWidth = cx;

	cx += ss->spacing;

	sw->slot = &ss->slots[ss->nSlots];
	sw->adjust = TRUE;

	ss->nSlots++;
    }

    totalWidth  += ss->spacing;
    totalHeight += ss->spacing;

    scaleW = (float) areaWidth / totalWidth;
    scaleH = (float) areaHeight / totalHeight;

    ss->scale = MIN (MIN (scaleH, scaleW), 1.0f);

    for (i = 0; i < ss->nWindows; i++)
    {
	SCALE_WINDOW (ss->windows[i]);

	if (sw->slot)
	{
	    ss->slots[i].y1 += ss->windows[i]->input.top;
	    ss->slots[i].x1 += ss->windows[i]->input.left;
 	    ss->slots[i].y2 += ss->windows[i]->input.top;
 	    ss->slots[i].x2 += ss->windows[i]->input.left;
	    ss->slots[i].y1 = (float) ss->slots[i].y1 * ss->scale;
	    ss->slots[i].x1 = (float) ss->slots[i].x1 * ss->scale;
 	    ss->slots[i].y2 = (float) ss->slots[i].y2 * ss->scale;
 	    ss->slots[i].x2 = (float) ss->slots[i].x2 * ss->scale;
	    ss->slots[i].x1 += areaX;
	    ss->slots[i].y1 += areaY;
 	    ss->slots[i].x2 += areaX;
 	    ss->slots[i].y2 += areaY;
	}
    }
DN(("END   layoutThumbs \n"));
    return TRUE;
}

static int
adjustScaleVelocity (CompWindow *w)
{
DN((" adjustScaleVelocity \n"));
    float dx, dy, ds, adjust, amount;
    float x1, y1, scale;

    SCALE_SCREEN (w->screen);
    SCALE_WINDOW (w);

    if (sw->slot)
    {
	x1 = sw->slot->x1;
	y1 = sw->slot->y1;
	scale = ss->scale;
    }
    else
    {
	x1 = w->serverX;
	y1 = w->serverY;
	scale = 1.0f;
    }

    dx = x1 - (w->serverX + sw->tx);

    adjust = dx * 0.15f;
    amount = fabs (dx) * 1.5f;
    if (amount < 0.5f)
	amount = 0.5f;
    else if (amount > 5.0f)
	amount = 5.0f;

    sw->xVelocity = (amount * sw->xVelocity + adjust) / (amount + 1.0f);

    dy = y1 - (w->serverY + sw->ty);

    adjust = dy * 0.15f;
    amount = fabs (dy) * 1.5f;
    if (amount < 0.5f)
	amount = 0.5f;
    else if (amount > 5.0f)
	amount = 5.0f;

    sw->yVelocity = (amount * sw->yVelocity + adjust) / (amount + 1.0f);

    ds = scale - sw->scale;

    adjust = ds * 0.1f;
    amount = fabs (ds) * 7.0f;
    if (amount < 0.01f)
	amount = 0.01f;
    else if (amount > 0.15f)
	amount = 0.15f;

    sw->scaleVelocity = (amount * sw->scaleVelocity + adjust) /
	(amount + 1.0f);

    if (fabs (dx) < 0.1f && fabs (sw->xVelocity) < 0.2f &&
	fabs (dy) < 0.1f && fabs (sw->yVelocity) < 0.2f &&
	fabs (ds) < 0.001f && fabs (sw->scaleVelocity) < 0.002f)
    {
	sw->xVelocity = sw->yVelocity = sw->scaleVelocity = 0.0f;
	sw->tx = x1 - w->serverX;
	sw->ty = y1 - w->serverY;
	sw->scale = scale;

	return 0;
    }

    return 1;
}

static Bool
scalePaintScreen (CompScreen		  *s,
		  const ScreenPaintAttrib *sAttrib,
		  Region		  region,
		  unsigned int		  mask)
{
DN(("scalePaintScreen \n"));
    Bool status;

    SCALE_SCREEN (s);

    if (ss->grabIndex)
	mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK;

    UNWRAP (ss, s, paintScreen);
    status = (*s->paintScreen) (s, sAttrib, region, mask);
    WRAP (ss, s, paintScreen, scalePaintScreen);

    return status;
}

static void
scalePreparePaintScreen (CompScreen *s,
			 int	     msSinceLastPaint)
{
DN((" scalePreparePaintScreen \n"));
    SCALE_SCREEN (s);

    if (ss->grabIndex && ss->state != SCALE_STATE_WAIT)
    {
	CompWindow *w;
	int        steps, dx, dy;
	float      amount, chunk;
    Eina_List *l;


	amount = msSinceLastPaint * 0.05f * ss->speed;
	steps  = amount / (0.5f * ss->timestep);
	if (!steps) steps = 1;
	chunk  = amount / (float) steps;

	while (steps--)
	{
	    ss->moreAdjust = 0;
        for(l = e_border_client_list(); l; l = l->next)
	    { 
	    	E_Border *bd;
	        bd = l->data;
	        w = findWindowAtDisplay (s->display, bd->win);
//	        w = bd->comp_win;
            
            SCALE_WINDOW (w);

		if (sw->adjust)
		{
		    sw->adjust = adjustScaleVelocity (w);

		    ss->moreAdjust |= sw->adjust;

		    sw->tx += sw->xVelocity * chunk;
		    sw->ty += sw->yVelocity * chunk;
		    sw->scale += sw->scaleVelocity * chunk;

		    dx = (w->serverX + sw->tx) - w->attrib.x;
		    dy = (w->serverY + sw->ty) - w->attrib.y;

		    moveWindow (w, dx, dy, FALSE, FALSE);

		    (*s->setWindowScale) (w, sw->scale, sw->scale);
		}

		if (!sw->adjust)
		{
			if (ss->selectedWindow != None)
			{
				if(ss->selectedWindow == w->id)
				{
					//if(ss->grabIndex > 0 && ss->state == SCALE_STATE_NONE)
					//{
					//	removeScreenGrab (s, ss->grabIndex, 0);
					//	ss->grabIndex = 0;
					//}
//					sendWindowActivationRequest (w->screen, w->id);
					addWindowDamage (w);
//					activateWindow (w);
					ss->selectedWindow=0;
					damageScreen (s);
				}
			}
		}

	    }

	    if (!ss->moreAdjust)
		break;
    	}
    }

    UNWRAP (ss, s, preparePaintScreen);
    (*s->preparePaintScreen) (s, msSinceLastPaint);
    WRAP (ss, s, preparePaintScreen, scalePreparePaintScreen);
}

static void
scaleDonePaintScreen (CompScreen *s)
{
DN(("BEGIN  scaleDonePaintScreen \n"));
   SCALE_SCREEN (s);

   if (ss->grabIndex)
   {
      if (ss->moreAdjust)
      {
         damageScreen (s);
      }
      else
      {
         if (ss->state == SCALE_STATE_IN)
         {
            ss->grabIndex = 0;
            ss->state = SCALE_STATE_NONE;
         }
         else if (ss->state == SCALE_STATE_OUT)
            ss->state = SCALE_STATE_WAIT;
         if(timer)
         {
            ecore_timer_del(timer);
            timer = NULL;
         }
      }
   }

   UNWRAP (ss, s, donePaintScreen);
   (*s->donePaintScreen) (s);
   WRAP (ss, s, donePaintScreen, scaleDonePaintScreen);
DN(("END   scaleDonePaintScreen \n"));
}

static CompWindow *
scaleCheckForWindowAt (CompScreen *s,
		       int        x,
		       int        y)
{
D(("BEGIN\n"));
   int        x1, y1, x2, y2;
   CompWindow *w;
   Eina_List *l;
   
   for (l = eina_list_last(s->windows); l; l = l->prev)
   {
      w = l->data;
      SCALE_WINDOW (w);

      if (sw->slot)
      {
         x1 = w->attrib.x - w->input.left * sw->scale;
         y1 = w->attrib.y - w->input.top  * sw->scale;
         x2 = w->attrib.x + (w->width  + w->input.right)  * sw->scale;
         y2 = w->attrib.y + (w->height + w->input.bottom) * sw->scale;

         if (x1 <= x && y1 <= y && x2 > x && y2 > y)
            return w;
      }
   }

   return 0;
}

static void
scaleInitiate (CompScreen *s, Bool CurrentOnly)
{
D(("BEGIN scaleInitiate\n"));
   SCALE_SCREEN (s);
   //SCALE_DISPLAY (s->display);

   if (ss->state != SCALE_STATE_WAIT /*&&
       ss->state != SCALE_STATE_OUT*/)
   {

      if (!layoutThumbs (s))
         return;

      ss->grabIndex = 1;
      ss->state = SCALE_STATE_OUT;
      damageScreen (s);
   
   }
   if(!timer)
      timer = ecore_timer_add(0.025, _e_scale_cb_redraw_timer, NULL);
}

static void
scaleTerminate (CompScreen *s)
{
D(("BEGIN scaleTerminate\n"));
   SCALE_DISPLAY (s->display);
   SCALE_SCREEN (s);
   CompWindow *w;
   Eina_List  *l;
   ss->allScreensMode=FALSE;
   ss->onlyCurrent=FALSE;
    
   if (ss->grabIndex)
   {
      if (ss->state == SCALE_STATE_NONE)
      {
         ss->grabIndex = 0;
      }
      else
      {
         for (l = s->windows; l; l = l->next)
         {
            w = l->data;
            SCALE_WINDOW (w);

            if (sw->slot)
            {
               sw->slot = 0;
               sw->adjust = TRUE;
            }
         }

         ss->state = SCALE_STATE_IN;
         damageScreen (s);
      }

      sd->lastActiveNum = None;
   }
   if(!timer)
      timer = ecore_timer_add(0.025, _e_scale_cb_redraw_timer, NULL);
}

static Bool
scaleHoverWindowAt (CompScreen *s,
		     int	 x,
		     int	 y)
	
{
D(("BEGIN\n"));
    CompWindow *w;

    w = scaleCheckForWindowAt (s, x, y);
    if(w)
    {
    	E_Border *bd = e_border_find_by_window(w->id);
    	
    	if(bd)
    	{
    		e_border_focus_set(bd, 1,1);
    		return TRUE;
    	}
    }
    return FALSE;
}

static Bool
scaleDamageWindowRect (CompWindow *w,
		       Bool	  initial,
		       BoxPtr     rect)
{
DN(("BEGIN\n"));
    Bool status;

    SCALE_SCREEN (w->screen);

    if (initial)
    {
	if (isScaleWin (w))
	{
	    if (ss->grabIndex && layoutThumbs (w->screen))
	    {
		ss->state = SCALE_STATE_OUT;
		damageScreen (w->screen);
	    }
	}
    }

    UNWRAP (ss, w->screen, damageWindowRect);
    status = (*w->screen->damageWindowRect) (w, initial, rect);
    WRAP (ss, w->screen, damageWindowRect, scaleDamageWindowRect);

    return status;
}



//static int _e_scale_cb_mouse_in(void *data, int ev_type, void *ev);
static int _e_scale_cb_mouse_move(void *data, int ev_type, void *ev);
static int _e_scale_cb_key_down(void *data, int ev_type, void *ev);
static int _e_scale_cb_mouse_button_down(void *data, int ev_type, void *ev);
//static int _e_scale_cb_border_focus_in(void *data, int ev_type, void *ev);
//static int _e_scale_cb_border_add(void *data, int ev_type, void *ev);
//static int _e_scale_cb_border_remove(void *data, int ev_type, void *ev);

//static int _e_scale_cb_window(void *data, int ev_type, void *ev);
Ecore_Event_Handler *handle_key = NULL;
Ecore_Event_Handler *handle_mouse_move = NULL;
Ecore_Event_Handler *handle_mouse_in = NULL;
Ecore_Event_Handler *handle_mouse_button_down = NULL;
Ecore_Event_Handler *handle_border_focus_in = NULL;
Ecore_Event_Handler *handle_border_add = NULL;
Ecore_Event_Handler *handle_border_remove = NULL;





static E_Border *
scaleSelectBorderAt (CompScreen *s,
		     int	 x,
		     int	 y)
	
{
D(("BEGIN\n"));
    CompWindow *w;
    SCALE_SCREEN(s);

    w = scaleCheckForWindowAt (s, x, y);
    if (w)
    {
    
       E_Border *bd = e_border_find_by_window(w->id);
       ss->selectedWindow = w->id; 
    	return bd; 
    }

    return NULL;
}


static int _e_scale_cb_key_down(void *data, int ev_type, void *ev)
{
DN(("!!!!!!!!!!!!!!!!!!!!! SCALE RECIEVES KEY DOWN !!!!!!!!!!!!!!!!!!!!"));

   Ecore_X_Event_Key_Down *e = ev;
   
   if(!strcmp(e->keyname,"F10"))
   {
      CompDisplay *d = data;
      CompScreen *s;
      SCALE_DISPLAY (d);
      s = findScreenAtDisplay (d,ecore_x_window_root_first_get());
      if (s)
      {
         input_window = ecore_x_window_input_new(s->root, 0, 0, 1, 1);
         ecore_x_window_show(input_window);
         if (!ecore_x_pointer_grab(input_window))
         {
            ecore_x_window_del(input_window);
            input_window = 0;
            return 0;
         }

         int state;
         SCALE_SCREEN (s);
         state = ss->state;
         ss->allScreensMode = TRUE;
         scaleInitiate (s,FALSE);
         //   handle_mouse_in = ecore_event_handler_add(ECORE_X_EVENT_MOUSE_IN, _e_scale_cb_mouse_in, d);
         if(!handle_mouse_move)
            handle_mouse_move = ecore_event_handler_add(ECORE_X_EVENT_MOUSE_MOVE, _e_scale_cb_mouse_move, d);
         if(!handle_mouse_button_down)   
            handle_mouse_button_down = ecore_event_handler_add(ECORE_X_EVENT_MOUSE_BUTTON_DOWN, _e_scale_cb_mouse_button_down, d);
      //  handle_border_focus_in = ecore_event_handler_add(E_EVENT_BORDER_FOCUS_IN, _e_scale_cb_border_focus_in, d);
       
      }
   }
   else if(!strcmp(e->keyname,"F11"))
   {
      CompDisplay *d = data;
      CompScreen *s;
      SCALE_DISPLAY (d);
      s = findScreenAtDisplay (d,ecore_x_window_root_first_get());
      if (s)
      {
         //  ecore_event_handler_del(handle_border_focus_in);
         int state;
         SCALE_SCREEN (s);
         state = ss->state;
         ss->allScreensMode = TRUE;
         scaleTerminate (s);

         ecore_x_pointer_ungrab();
         ecore_x_window_del(input_window);
         input_window = 0;
         if(handle_mouse_move)
         {
            ecore_event_handler_del(handle_mouse_move);
            handle_mouse_move = NULL;   
         }
         // if(handle_mouse_in) {
         //    ecore_event_handler_del(handle_mouse_in);
         //    handle_mouse_in = NULL;}
         if(handle_mouse_button_down)
         {
            ecore_event_handler_del(handle_mouse_button_down);
            handle_mouse_button_down = NULL;
         }
      }
   }   
return 1;
}

//~ static int _e_scale_cb_border_focus_in(void *data, int ev_type, void *ev)
//~ {
	//~ CompDisplay *d = data;
	//~ E_Event_Border_Focus_In *e = ev;
	//~ CompWindow *w = findWindowAtDisplay (d, e->border->win);
//~ }


static int _e_scale_cb_mouse_button_down(void *data, int ev_type, void *ev)
{
 	Ecore_X_Event_Mouse_Button_Down *e = ev;
    CompDisplay *d = data;
 	CompScreen *s = findScreenAtDisplay (d, ecore_x_window_root_first_get());
	D(("!!!!!!!!!!!!!!!!!!!!! SCALE RECIEVES MOUSE DOWN !!!!!!!!!!!!!!!!!!!!"));
	if (s)
	{
	    int state;

	    SCALE_SCREEN (s);

	    state = ss->state;

	    if (ss->grabIndex && ss->state != SCALE_STATE_IN && e->button == 1)
	    {
	    	
	    E_Border *bd = 	scaleSelectBorderAt (s,
					 e->root.x,
					 e->root.y);
		if (bd)
		{
			
			if(bd->desk != e_desk_current_get(bd->desk->zone))
				e_desk_show(bd->desk);
		    
		    e_border_raise(bd);
		
			
			Eina_List *l;
     		for(l = e_border_client_list(); l; l = l->next)

	        { 
	    	E_Border *bd;
	        bd = l->data;
	        if(!bd)
	         continue;
	         
	        if(bd->desk != e_desk_current_get(bd->desk->zone)) //TODO all desk mode
	           e_border_hide(bd,0);
	        }
	        
	        
		 

	      if(handle_mouse_move) {
	       ecore_event_handler_del(handle_mouse_move);
	       handle_mouse_move = NULL;
	       }

	 	 if(handle_mouse_button_down){  
	 	    ecore_event_handler_del(handle_mouse_button_down);
	 	    handle_mouse_button_down = NULL;
	 	    }		
		   
		    scaleTerminate (s);
		 
		ecore_x_pointer_ungrab();
	    ecore_x_window_hide(input_window);
	    ecore_x_window_del(input_window);
	    input_window = 0;	
		}
		
		// incomplete !!!!!!!
	    }
	}
	return 1;
}

static int _e_scale_cb_mouse_move(void *data, int ev_type, void *ev)
{
D(("!!!!!!!!!!!!!!!!!!!!! SCALE RECIEVES MOUSE MOVE !!!!!!!!!!!!!!!!!!!!"));
 Ecore_X_Event_Mouse_Move     *e;	 
 CompScreen *s;
 CompDisplay *d = data;
 e = ev; 
 s = findScreenAtDisplay (d, ecore_x_window_root_first_get());
	if (s)
	{
	    SCALE_SCREEN (s);

	    if (ss->grabIndex		    &&
		ss->state != SCALE_STATE_IN &&
		SCALE_SLOPPY_FOCUS_DEFAULT)
		    scaleHoverWindowAt (s,e->root.x,e->root.y);
	}
   return 1;
}

//~ static int _e_scale_cb_mouse_in(void *data, int ev_type, void *ev)
//~ {
	//~ Ecore_X_Event_Mouse_In  *e;	 
 	//~ CompScreen *s;
 	//~ CompDisplay *d = data;
 	//~ e = ev; 
	
	
	//~ if (e->mode != ECORE_X_EVENT_MODE_UNGRAB   &&
	    //~ e->mode != ECORE_X_EVENT_MODE_GRAB     &&
	    //~ e->detail != ECORE_X_EVENT_DETAIL_INFERIOR)
	//~ {
	    //~ s = findScreenAtDisplay (d, ecore_x_window_root_first_get());
	    //~ if (s)
	    //~ {
			//~ unsigned int i;
			//~ Window       id = e->event_win;

			//~ SCALE_SCREEN (s);

			//~ for (i = 0; i < 4; i++)
			//~ {
		    	//~ if (id == s->screenEdge[scaleEdge[i]].id)
		    	//~ {
					//~ if (ss->cornerScreenMask & (1 << i))
					//~ {
			    		//~ if(ss->state == SCALE_STATE_OUT || ss->state == SCALE_STATE_WAIT)
				    		//~ scaleTerminate (s);
			    		//~ else
				    		//~ scaleInitiate (s,FALSE);
					//~ }
                	//~ else if (ss->cornerApplicationMask & (1 << i))
					//~ {
			    		//~ if(ss->state == SCALE_STATE_OUT || ss->state == SCALE_STATE_WAIT)
				    		//~ scaleTerminate (s);
			    		//~ else
				    		//~ scaleInitiate (s,TRUE);
					//~ }
            		//~ else if (ss->cornerAllScreensMask & (1 << i))
					//~ {
			    		//~ if(ss->state == SCALE_STATE_OUT || ss->state == SCALE_STATE_WAIT) 
			    		//~ {
				    		//~ scaleTerminate (s);
			    		//~ } 
			    		//~ else 
			    		//~ {
				    		//~ ss->allScreensMode = TRUE;
				    		//~ scaleInitiate (s,FALSE);
			    		//~ }
					//~ }
           		//~ }
			//~ }
	    //~ }
	//~ }
	//~ D(("END MOUSE IN\n"));
	//~ return 1;
//~ }


static int _e_scale_cb_redraw_timer(void *data){
DN(("////////////// BEGIN _e_scale_cb_redraw_timer /////////// \n")); 
   bang_redraw(FALSE);
   return 1; //repeat the timer
}

static Bool
scaleInitDisplay (CompPlugin  *p, CompDisplay *d)
{
D(("BEGIN\n"));
   ScaleDisplay *sd;

   sd = malloc (sizeof (ScaleDisplay));
   if (!sd)
      return FALSE;

   sd->screenPrivateIndex = allocateScreenPrivateIndex (d);
   if (sd->screenPrivateIndex < 0)
   {
      free (sd);
      return FALSE;
   }

   sd->lastActiveNum = None;

////    WRAP (sd, d, handleEvent, scaleHandleEvent);

   d->privates[displayPrivateIndex].ptr = sd;

  // handle_key = ecore_event_handler_add(ECORE_X_EVENT_KEY_DOWN, _e_scale_cb_key_down, d);
  // handle_border_add = ecore_event_handler_add(E_EVENT_BORDER_ADD, _e_scale_cb_border_add, d);
  // handle_border_remove = ecore_event_handler_add(E_EVENT_BORDER_REMOVE, _e_scale_cb_border_remove, d);
    return TRUE;
}

static void
scaleFiniDisplay (CompPlugin  *p,
                  CompDisplay *d)
{
D(("scaleFiniDisplay\n"));
    SCALE_DISPLAY (d);

    freeScreenPrivateIndex (d, sd->screenPrivateIndex);

 ////   UNWRAP (sd, d, handleEvent);

    free (sd); 
}

static Bool
scaleInitScreen (CompPlugin *p,
                 CompScreen *s)
{
printf(("scaleInitScreen\n"));
    ScaleScreen *ss;

    SCALE_DISPLAY (s->display);

    ss = malloc (sizeof (ScaleScreen));
    if (!ss)
	return FALSE;

    ss->windowPrivateIndex = allocateWindowPrivateIndex (s);
    if (ss->windowPrivateIndex < 0)
    {
	free (ss);
	return FALSE;
    }

    ss->grabIndex = 0;

    ss->state = SCALE_STATE_NONE;

    ss->slots = 0;
    ss->slotsSize = 0;

    ss->windows = 0;
    ss->windowsSize = 0;

    ss->line = 0;
    ss->lineSize = 0;

    ss->scale = 1.0f;

    ss->spacing  = SCALE_SPACING_DEFAULT;
    ss->speed    = SCALE_SPEED_DEFAULT;
    ss->timestep = SCALE_TIMESTEP_DEFAULT;
    ss->opacity  = (OPAQUE * SCALE_OPACITY_DEFAULT) / 100;

    ss->onlyCurrent = FALSE;
    ss->selectedWindow = None;
    ss->allScreensMode = FALSE;

    ss->head = 0;

    scaleSetHead ( ss, s, SCALE_HEAD_DEFAULT - 1 );

    ss->iconOverlay = ScaleIconEmblem;


    WRAP (ss, s, preparePaintScreen, scalePreparePaintScreen);
    WRAP (ss, s, donePaintScreen, scaleDonePaintScreen);
    WRAP (ss, s, paintScreen, scalePaintScreen);
    WRAP (ss, s, paintWindow, scalePaintWindow);
    WRAP (ss, s, damageWindowRect, scaleDamageWindowRect);

    s->privates[sd->screenPrivateIndex].ptr = ss;

    return TRUE;
}

static void
scaleFiniScreen (CompPlugin *p,
                 CompScreen *s)
{
D(("scaleFiniScreen\n"));
    SCALE_SCREEN (s);

    UNWRAP (ss, s, preparePaintScreen);
    UNWRAP (ss, s, donePaintScreen);
    UNWRAP (ss, s, paintScreen);
    UNWRAP (ss, s, paintWindow);
    UNWRAP (ss, s, damageWindowRect);

    if (ss->slotsSize)
	free (ss->slots);

    if (ss->lineSize)
	free (ss->line);

    if (ss->windowsSize)
	free (ss->windows);

    free (ss);
}

static Bool
scaleInitWindow (CompPlugin *p,
                 CompWindow *w)
{
D(("scaleInitWindow\n"));
   ScaleWindow *sw;

   SCALE_SCREEN (w->screen);

   sw = malloc (sizeof (ScaleWindow));
   if (!sw)
      return FALSE;

   sw->slot = 0;
   sw->scale = 1.0f;
   sw->tx = sw->ty = 0.0f;
   sw->adjust = FALSE;
   sw->xVelocity = sw->yVelocity = 0.0f;
   sw->scaleVelocity = 1.0f;

   w->privates[ss->windowPrivateIndex].ptr = sw;

   return TRUE;
}

static void
scaleFiniWindow (CompPlugin *p,
                 CompWindow *w)
{
D(("scaleFiniWindow\n"));
   SCALE_WINDOW (w);
   if (sw)
    free (sw);
}


static E_Action *act = NULL;

static void
_scale_action_cb(E_Object *obj, const char *params)
{
   D(("BEGIN _scale_action_cb\n"));
}
static void
_scale_action_mouse_cb(E_Object *obj, const char *params, Ecore_X_Event_Mouse_Button_Down *ev)
{
   D(("BEGIN _scale_action_mouse_cb\n"));
}
static void
_scale_action_key_cb(E_Object *obj, const char *params, Ecore_X_Event_Key_Down *ev)
{
   D(("BEGIN _scale_action_key_cb\n"));
   
   CompDisplay *d = bang->displays->data;
   CompScreen  *s;
   s = findScreenAtDisplay(d,ecore_x_window_root_first_get());
   SCALE_SCREEN(s);
   if (ss->state == SCALE_STATE_NONE ||
       ss->state == SCALE_STATE_OUT)
      scaleInitiate(s, TRUE);
   else
      scaleTerminate(s);
}

static Bool
scaleInit (CompPlugin *p)
{
D(("scaleInit\n"));
   displayPrivateIndex = allocateDisplayPrivateIndex ();
   if (displayPrivateIndex < 0)
      return FALSE;
    
   act = e_action_add("scale");
   if (act)
   {
      act->func.go = _scale_action_cb;
      act->func.go_mouse = _scale_action_mouse_cb;
      act->func.go_key = _scale_action_key_cb;
      e_action_predef_name_set("Bang", "Show all windows",
                                 "scale", NULL, NULL, 0);
   }
   
   return TRUE;
}

static void
scaleFini (CompPlugin *p)
{
D(("BEGIN scaleFini\n"));
    
   if (act)
   {
      e_action_predef_name_del("Bang", "Show all windows");
      e_action_del("scale");
      act = NULL;
   }
   
   if (displayPrivateIndex >= 0)
      freeDisplayPrivateIndex (displayPrivateIndex);
   
}

CompPluginVTable scaleVTable = {
   "scale",
   "Scale",
   "Scale windows",
   scaleInit,
   scaleFini,
   scaleInitDisplay,
   scaleFiniDisplay,
   scaleInitScreen,
   scaleFiniScreen,
   scaleInitWindow,
   scaleFiniWindow
};

CompPluginVTable *
getCompPluginInfo (void)
{
    return &scaleVTable;
}
